/*
 *  AlgorithmFunctions.cpp
 *  GRE
 *
 *  Created by Kutlak Roman on 28/08/2011.
 *  Copyright 2011 University of Aberdeen. All rights reserved.
 *
 */

#include <iostream>
#include <algorithm>

#include "AlgorithmFunctions.h"

using std::cout;
using std::cerr;
using std::endl;

namespace kutlak {
namespace roman {
namespace gre {
    
    
#pragma mark -
#pragma mark functions
    //=============================  functions  ===================================
    
    /* Count how many entities are ruled out by the given property.
     *
     */
    int RulesOut(const ContainerT& distractors, const Property& property)
    {
        int result = 0;
        
        if (property.Attribute() == DEFAULT_ATTRIBUTE ||
            property.Value() == DEFAULT_VALUE)
            return 0;
        

        for_each (distractors.begin(), distractors.end(),
                  [&result, &property](const ContainerT::value_type& it)
        {
            if (!(it)->HasProperty(property))
                ++result;
        });
        
        return result;
    }
    
    
    int Filter(ContainerT& distractors, const EntityPtrT& entity)
    {
        int nErased = distractors.size();
        
        if (!entity)
            return 0;
        
        EntityPtrEqPred pred(entity);
        ContainerT::iterator end;
        // Drop the entities that match this one.
        end = std::remove_if(distractors.begin(), distractors.end(), pred);
        distractors.resize(end - distractors.begin());
        
        nErased -= distractors.size();
        
        return nErased;
    }
    
    // Remove entities without the property from the list.
    int Filter(ContainerT& distractors, const Property& property)
    {
        int nErased = distractors.size();
        
        if (property.Value() == DEFAULT_VALUE ||
            property.Attribute() == DEFAULT_ATTRIBUTE)
            return 0;
        
        DoesNotHavePropertyPred pred(property);
        ContainerT::iterator end;
        // Drop the entities that don't have the given property.
        end = std::remove_if(distractors.begin(), distractors.end(), pred);
        distractors.resize(end - distractors.begin());
        
        nErased -= distractors.size();
        
        return nErased;
    }
    
    /* Remove entities without the value from the list.
     * If a property does not have the attribute specified by the property
     * it is not removed, as we don't know whether the entity has the property
     * at all. Kind of open-world assumption.
     */
    int SoftFilter(ContainerT& distractors, const Property& property)
    {
        int nErased = distractors.size();
        
        if (property.Value() == DEFAULT_VALUE ||
            property.Attribute() == DEFAULT_ATTRIBUTE)
            return 0;
        
        DoesNotHaveValuePred pred(property);
        ContainerT::iterator end;
        // Drop the entities that don't have the given property.
        end = std::remove_if(distractors.begin(), distractors.end(), pred);
        distractors.resize(end - distractors.begin());
        
        nErased -= distractors.size();
        
        return nErased;
    }
    
    /* Compute the discriminatory power or a property relatively to some list.
     *
     */
    double DiscriminatoryPower(const ContainerT& distractors,
                               const Property& property, 
                               const int m)
    {
        /* "Defautl Value" is an internal value used in the default constructor.
         *  The value should be treated as NULL or something similar.
         */
        if (property.Value() == DEFAULT_VALUE ||
            property.Attribute() == DEFAULT_ATTRIBUTE)
            return 0.0;
        
        // Count how many distractors have this property.
        int n = 0;    
        for (auto it = distractors.begin(); it != distractors.end(); ++it) {
            if ((*it)->HasProperty(property))
                ++n;
        }
        
        /*! Discriminatory power calculated as:
         * N-n/N-m
         * where N is the number of entities being considered
         * and n is number of entities having the property
         * and m is the number of entities being singled out
         * (m = 1 for one referent and so on)
         */
        double N = (double) distractors.size();
        if (N - m == 0) // when there are only m objects in the database
            N += m;     // make sure we calculate the correct value
        
        double result = ( static_cast<double>( N - n ) / static_cast<double>(N - m) );
        
        // if a property does not occur in the knowledgebase it will have
        // discriminatory power above 1.0 and so we have to limit it in order
        // to conform to the function definition (0.0 <= result <= 1.0)
        return (result>1.0?1.0:result);
    }
    
    /* Check wether a property has a discriminatory power > 0 relatively to the 
     * list of distractors.
     */
    bool HasDiscriminatoryPower(const ContainerT& distractors, 
                                const Property& property)
    {
        // Since type has to be included in every description, treat it as
        // always distinguishing.
        if (property.Attribute() == Ontology::kType)
            return true;

        
        // Default Value is reserved value, not used in the knowledge base. 
        // Whenever encountered, it should be treated as a NULL value or
        // something similar.
        if (property.Value() == DEFAULT_VALUE ||
            property.Attribute() == DEFAULT_ATTRIBUTE)
            return false;
        
        // Go through the set of distractors. If at least one of them does not
        // have the given property then the property has 'some' discriminatory power.
        for (auto it = distractors.begin(); it != distractors.end(); ++it) {
            if (!(*it)->HasProperty(property)) {
                return true;
            }
        }   // end of for loop on distractors 
        
        // If all of the distractors have the property, it has no discriminatory power.
        return false;
    }
    
    ContainerT Extension(ContainerT distractors, const Property& p)
    {
        DoesNotHavePropertyPred pred(p);

        // Drop the entities that don't have the given property.
        auto end = std::remove_if(distractors.begin(), distractors.end(), pred);
        distractors.resize(end - distractors.begin());
        
        return distractors;
    }
    
    
    size_t ExtensionSize(const ContainerT& distractors, const Property& p)
    {
        HasPropertyPred pred(p);

        // Count the entities that have the given property.
        size_t result  = std::count_if(distractors.begin(), distractors.end(), pred);
        
        return result;
    }
    
    ContainerT Extension(ContainerT distractors, const RefExp& re)
    {
        DoesNotHavePropertiesPred pred(re);

        // Drop the entities that don't have the given property.
        auto end = std::remove_if(distractors.begin(), distractors.end(), pred);
        distractors.resize(end - distractors.begin());
        
        return distractors;
    }
    
    
    size_t ExtensionSize(const ContainerT& distractors, const RefExp& re)
    {
        size_t result = distractors.size();
        
        for (auto e = distractors.begin(); e != distractors.end(); ++e)
        {
            RefExp::const_iterator p;
            for (p = re.begin(); p != re.end(); ++p)
            {
                if (!(*e)->HasProperty(*p))
                {
                    --result;
                    break;
                }
            }
        }
        
        return result;
    }
    
    
    
    /* Find the least specific value that rules most of the distractors.
     * i.e. if more specific value Y rules out more distractors than 
     * less specific value X then use Y. Use X otherwise.
     */
    Property FindBestValue(const ContainerT& distractors, 
                           const EntityPtrT& referent, 
                           const Property& property)
    {
        if (!referent)
        {
            cerr << __FILE__ << " (" << __LINE__ << "): ";
            cerr << "Referent not set." << endl;
            return property;
        }
        
        double max = -1.0;
        std::vector<std::pair<Property, double> > props;
        
        for (auto prop = referent->begin(); prop != referent->end(); ++prop) 
        {
            Property q(*prop);
            if (property.Attribute() == q.Attribute()) {
                double tmp = DiscriminatoryPower(distractors, q);
                if (tmp > max)
                    max = tmp;
                props.push_back(std::make_pair(q, tmp));
            }
        }
        
        pOntology onto = Ontology::Default();
        if (onto)
        { // ontology set
            // sort the list by specificity as given by taxonomy
            sort(props.begin(), props.end(), SubsumptionComparator(onto));
        }
        
        
        if (max > 0.0)
        {
            for (int i = 0; i < props.size(); ++i)
            {
                if (props[i].second == max)
                    return props[i].first;
            }
        }
        
        return property;
    }
    

} // namespace gre
} // namespace roman
} // namespace kutlak
